#include <cstring>
#include <cerrno>

#include <fcntl.h>
#include <unistd.h>

#include <arpa/inet.h>

#include "socket.h"

SocketException::SocketException(): code(errno) {}
SocketException::operator char*() { return strerror(code); }

Socket::Socket(int domain, int type, int protocol) {
	if ((fd = socket(domain, type, protocol)) == -1) {
		throw SocketException();
	}
}

Socket::~Socket() {
	close(fd);
}

void Socket::ReuseAddr(bool value) {
	int sopt = value;
	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &sopt, sizeof(sopt)) == -1) {
		throw SocketException();
	}
}

void Socket::Blocking(bool value) {
	int flags = fcntl(fd, F_GETFL);
	if (flags == -1) {
		throw SocketException();
	}
	if (value) {
		flags &= ~O_NONBLOCK;
	} else {
		flags |= O_NONBLOCK;
	}
	if (fcntl(fd, F_SETFL, flags) == -1) {
		throw SocketException();
	}
}

void Socket::Bind(unsigned short port, unsigned long addr) {
	struct sockaddr_in ba;
	ba.sin_family = AF_INET;
	ba.sin_port = htons(port);
	ba.sin_addr.s_addr = htonl(addr);

	if (bind(fd, (struct sockaddr*)&ba, sizeof(ba)) == -1) {
		throw SocketException();
	}
}

void Socket::Listen(int backlog) {
	if (listen(fd, backlog) == -1) {
		throw SocketException();
	}
}

Socket* Socket::Accept() {
	int sfd = accept(fd, 0, 0);
	if (sfd == -1) {
		if (errno = EWOULDBLOCK) {
			return NULL;
		}
		throw SocketException();
	}
	return new Socket((void*)0, sfd);
}

void Socket::Connect(const char* addr, unsigned short port) {
	struct sockaddr_in ca;
	ca.sin_family = AF_INET;
	ca.sin_port = htons(port);

	if (inet_aton(addr, &ca.sin_addr) == -1) {
		throw SocketException();
	}
	if (connect(fd, (struct sockaddr*)&ca, sizeof(ca)) == -1) {
		throw SocketException();
	}
}

ssize_t Socket::Receive(void* buf, size_t nbytes) {
	ssize_t result = read(fd, buf, nbytes);
	if (result == -1) {
		if (errno == EAGAIN) {
			return -1;
		}
		throw SocketException();
	}
	return result;
}

ssize_t Socket::Send(const void* buf, size_t nbytes) {
	ssize_t result = write(fd, buf, nbytes);
	if (result == -1) {
		throw SocketException();
	}
	return result;
}
